#include "stdafx.h"

#include "Exporter.h"

#include "ExNode.h"
#include "ExMesh.h"
#include "ExPortal.h"
#include "ExHullMesh.h"

#include "stringutility.h"
#include "3rd_part\xml\TiXmlUtility.h"

#include "axisalignedbox.h"

#include "MaxExporter.h"

extern CMaxExporterApp theApp;

int				Exporter::ExtCount()
{
	return 1;
}

const TCHAR *	Exporter::Ext(int n)
{
	switch( n )
	{
	case 0:
		return _T( "VisualObj" );
	}
	return _T("");
}

const TCHAR *	Exporter::LongDesc()
{
	return _T("Visual Object Export");
}

const TCHAR *	Exporter::ShortDesc()
{
	return _T("Visual Object Export");
}

const TCHAR *	Exporter::AuthorName()
{
	return _T("Chris");
}

const TCHAR *	Exporter::CopyrightMessage()
{
	return _T("");
}

const TCHAR *	Exporter::OtherMessage1()
{
	return _T("");
}

const TCHAR *	Exporter::OtherMessage2()
{
	return _T("");

}

unsigned int	Exporter::Version()
{
	return 100;
}

void Exporter::ShowAbout(HWND hWnd)
{
}

Exporter::Exporter()
	:m_pRoot(NULL),m_iStaticFrame(0)
{
}

Exporter::~Exporter()
{
	_ResetAll();
}

void Exporter::_ResetAll( void )
{
	for( UINT uIdx = 0; uIdx != m_VisualMeshes.size(); ++uIdx )
		delete m_VisualMeshes[uIdx];
	
	for( UINT uIdx = 0; uIdx != m_VisualPortals.size(); ++uIdx )
		delete m_VisualMeshes[uIdx];

	for( UINT uIdx = 0; uIdx != m_HullMeshes.size(); ++uIdx )
		delete m_VisualMeshes[uIdx];

	m_VisualMeshes.clear();
	m_VisualPortals.clear();
	m_HullMeshes.clear();

	delete m_pRoot;
	m_pRoot = NULL;

	m_iStaticFrame = 0;
}

void Exporter::_PreProcess( ExNode* pParent )
{
	//process parent node
	INode* pMaxNodeParent = pParent->GetMaxNode();
	if( pParent->GetParent() != NULL && pMaxNodeParent->Renderable() )
	{
		BOOL bIsPortal = FALSE;
		pMaxNodeParent->GetUserPropBool( "portal", bIsPortal );

		if( bIsPortal == TRUE )
			m_PortalNodes.push_back( pMaxNodeParent );
		else
			m_MeshNodes.push_back( pMaxNodeParent );
	}
	//recursive children
	for( int iIdx = 0 ; iIdx != pMaxNodeParent->NumChildren(); ++iIdx )
	{
		INode* pChild = pMaxNodeParent->GetChildNode( iIdx );
		if( pChild == NULL )
			continue;

		ExNode* pChildNode = new ExNode( pChild );
		pParent->AddChildNode(pChildNode);

		this->_PreProcess( pChildNode );
	}
}

int	Exporter::DoExport(const TCHAR *name,ExpInterface *ei,Interface* pInterface, BOOL suppressPrompts, DWORD options)
{
	if( theApp.GetDevice() == NULL && !theApp.InitD3DDevice() )
		return 0;

	//update static frame
	m_iStaticFrame = pInterface->GetTime() / GetTicksPerFrame();

	//construct Ex Tree
	INode* pRoot = pInterface->GetRootNode();
	if( pRoot == NULL )
		return 0;

	m_pRoot = new ExNode(pRoot);
	_PreProcess( m_pRoot );

	MSTR& fileName = pInterface->GetCurFileName();

	TiXmlDocument pDoc;
	m_strFileName = name;
	m_strFilePath = name;

	m_strFileName.erase( m_strFileName.begin(), m_strFileName.begin() + m_strFileName.rfind( "\\" ) + 1 );
	m_strFilePath.erase( m_strFilePath.begin() + m_strFilePath.rfind( "\\" ) + 1, m_strFilePath.end() );

	std::auto_ptr< TiXmlNode > ptrVisualNode ( new TiXmlElement( m_strFileName ) );
	
	// Snapping is now a flagable option for static objects
	_ExportHierachy( ptrVisualNode.get() );
	
	_ExtractMeshes(true, ptrVisualNode.get());
	_ExtractPortals(true);

	// Calculate the bounding box of visual object
	Common::AxisAlignedBox rBoundingbox;
	_UpdateBoundingBox( rBoundingbox );

	// Export hull tree for all objects if there is a custom hull defined.
	std::auto_ptr< TiXmlElement > ptrHullSection ( new TiXmlElement("Hull") );
	if (!m_HullMeshes.empty())
	{
		_ExportHull( ptrHullSection.get() );
		ptrHullSection->SetAttribute("customHull","true");
	}
	else
	{
		_GenerateHull( ptrHullSection.get(), rBoundingbox );
		ptrHullSection->SetAttribute("customHull","false");
	}
	// Now write any explicitly set portal nodes.
	// The following method finds boundaries for our portals
	// and writes them into the appropriate boundary sections.
	_ExportPortalsToBoundaries( ptrHullSection.get() );

	ptrVisualNode->InsertEndChild( *ptrHullSection.get() );

	//write boundingbox
	TiXmlElement boundBox("boundingBox");
	TiXmlUtility::WriteAABB( ExUtilityFunction::MaxToDx( ExUtilityFunction::ApplyUnitScale(rBoundingbox) ), &boundBox );
	ptrVisualNode->InsertEndChild( boundBox );

	pDoc.InsertEndChild(*ptrVisualNode.get());

	try
	{
		if( !pDoc.SaveFile(name) )
		{
			::MessageBox(	0,
				_T("Failed to save the fail!"),
				_T("Export error!"),
				MB_ICONERROR );
		}
	}
	catch(...)
	{
		assert( false );
	}
	//clear all for reenter
	_ResetAll();

	::MessageBox(	0,
				_T("Done!"),
				_T("Export success!"),
				MB_ICONINFORMATION );

	return true;
}

void Exporter::_UpdateBoundingBox( Common::AxisAlignedBox& rBoundingbox )
{
	std::vector<ExMesh*>::iterator itrMesh = m_VisualMeshes.begin();
	for(; itrMesh != m_VisualMeshes.end(); ++itrMesh )
	{
		const Common::AxisAlignedBox& rBB = (*itrMesh)->GetBoundingBox();
		rBoundingbox.merge(rBB);
	}

	itrMesh = m_HullMeshes.begin();
	for(; itrMesh != m_HullMeshes.end(); ++itrMesh )
	{
		const Common::AxisAlignedBox& rBB = (*itrMesh)->GetBoundingBox();
		rBoundingbox.merge(rBB);
	}
}

void Exporter::_GenerateHull( TiXmlNode* pHullSection, const Common::AxisAlignedBox& bb )
{
	//generate hull by bounding box
	Common::Plane rBoundary;
	for( UINT uFaceIdx = 0; uFaceIdx != 6; ++uFaceIdx )
	{
		TiXmlElement rBoundarySection("boundary");

		Common::Vector3 vNormal( Common::Vector3::ZERO );
		if( uFaceIdx < 3 )
		{
			vNormal[uFaceIdx] = -1.0f;
			rBoundary.redefine( vNormal,  bb.getMaximum() );
		}
		else
		{
			vNormal[uFaceIdx % 3] = 1.0f;
			rBoundary.redefine( vNormal, bb.getMinimum() );
		}

		TiXmlUtility::WritePlane( ExUtilityFunction::MaxToDx( ExUtilityFunction::ApplyUnitScale( rBoundary ) ), &rBoundarySection );

		pHullSection->InsertEndChild( rBoundarySection );
	}
}

void Exporter::_ExportHull( TiXmlNode* pHullSection )
{
	//merge them all
	ExHullMesh accHullMesh;

	std::vector<ExMesh*>::iterator pos = m_HullMeshes.begin();
	for(; pos != m_HullMeshes.end(); ++pos )
		accHullMesh.Merge( *pos );

	if( !accHullMesh.Save( pHullSection ) )
		assert( false );
}

void Exporter::_ExportHierachy( TiXmlNode* pVisualSection )
{
	std::auto_ptr< TiXmlNode > ptrHierachySection( new TiXmlElement("Hierachy") );
	m_pRoot->Save( ptrHierachySection.get() );
	pVisualSection->InsertEndChild(*ptrHierachySection.release());
}

void Exporter::_ExtractMeshes( bool snapVertices, TiXmlNode* pVisualSection )
{
	INodeVector::iterator itrMeshNode = m_MeshNodes.begin();
	for(; itrMeshNode != m_MeshNodes.end(); ++itrMeshNode )
	{
		assert( (*itrMeshNode) != NULL );

		ObjectState os = (*itrMeshNode)->EvalWorldState( m_iStaticFrame * GetTicksPerFrame() );

		if( os.obj && ( os.obj->SuperClassID() != GEOMOBJECT_CLASS_ID ) )
			continue;

		Object *obj = os.obj;
		if (!obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0) ) )
			continue;

		TriObject* pTri = (TriObject *) obj->ConvertToType( m_iStaticFrame, Class_ID(TRIOBJ_CLASS_ID, 0) );

		ExMesh* pMesh = new ExMesh;
		if( !pMesh->Init( *itrMeshNode, &pTri->mesh, m_iStaticFrame * GetTicksPerFrame(), snapVertices ) )
			ASSERT(false);

		BOOL bHullMesh = FALSE;
		(*itrMeshNode)->GetUserPropBool( "Hull", bHullMesh );
		if( bHullMesh )
			m_HullMeshes.push_back(pMesh);
		else
			m_VisualMeshes.push_back(pMesh);
	}

	std::vector<ExMesh*>::iterator itrMesh = m_VisualMeshes.begin();
	TiXmlNode* pGeomElement = pVisualSection->InsertEndChild( TiXmlElement("Geometry") );

	for( ; itrMesh != m_VisualMeshes.end(); ++itrMesh )
		(*itrMesh)->Save( m_strFilePath, pGeomElement );
}

void Exporter::_ExtractPortals( bool snapVertices )
{
	INodeVector::iterator itrPortalNode = m_PortalNodes.begin();
	for(; itrPortalNode != m_PortalNodes.end(); ++itrPortalNode )
	{
		if( (*itrPortalNode)->IsHidden() )
			continue;
		//Get triangulated object.
		ObjectState os = (*itrPortalNode)->EvalWorldState( m_iStaticFrame * GetTicksPerFrame() );

		if( os.obj && ( os.obj->SuperClassID() != GEOMOBJECT_CLASS_ID ) )
			continue;

		Object *obj = os.obj;
		if (!obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0) ) )
			continue;

		TriObject* pTri = (TriObject *) obj->ConvertToType( m_iStaticFrame, Class_ID(TRIOBJ_CLASS_ID, 0) );
		
		ExPortal* pPortal = new ExPortal;
		if( !pPortal->Init(*itrPortalNode, &pTri->mesh, m_iStaticFrame * GetTicksPerFrame(), snapVertices) )
			continue;

		m_VisualPortals.push_back(pPortal);
	}
}

void Exporter::_ExportPortalsToBoundaries( TiXmlNode* pHullSection )
{
	if (!m_VisualPortals.size())
		return;

	// store the boundaries as plane equations
	std::vector<TiXmlHandle> boundarySections;
	TiXmlHandle ptrHullSection( pHullSection );
	int iChildIdx = 0;
	while(1)
	{
		TiXmlHandle ptrBoundarySection = ptrHullSection.Child("boundary", iChildIdx++);
		if( ptrBoundarySection.ToElement() == NULL )
			break;
		boundarySections.push_back(ptrBoundarySection);
	}

	std::vector<Common::Plane> boundaries;
	for ( int iBoundaryIdx = 0; iBoundaryIdx < boundarySections.size(); iBoundaryIdx++ )
	{
		//revise to the max coordinates and unapply scaling
		Common::Plane boundary = TiXmlUtility::ReadPlane( boundarySections[iBoundaryIdx] );
		boundary.d *=  100 / (float)GetMasterScale( UNITS_CENTIMETERS );
		boundary = ExUtilityFunction::DxToMax( boundary );

		boundaries.push_back( boundary );
	}

	// now look at all our portals, and assign them to a boundary
	bool bEachOnBoundary = true;
	bool bPortalCulled = false;

	for( int iPortalIdx = 0 ; iPortalIdx != m_VisualPortals.size(); ++iPortalIdx )
	{
		ExPortal* pPortal = m_VisualPortals[iPortalIdx];
		bool bTempPortalCulled = false;
		if( !pPortal->CullHull( boundaries, bTempPortalCulled ) )
		{
			m_VisualPortals.erase( m_VisualPortals.begin() + iPortalIdx-- );
			bEachOnBoundary = false;
			continue;
		}
		else
		{
			int iPortalBoundary = pPortal->OnBoundary( boundaries );
			if ( -1 != iPortalBoundary )
			{
				bPortalCulled |= bTempPortalCulled;
				pPortal->Save( boundarySections[iPortalBoundary].ToNode() );
			}
			else
				bEachOnBoundary = false;
		}
	}
	m_VisualPortals.clear();

	// Warn the user that one or more of the exported portals were culled in some way
	if ( bPortalCulled )
		::MessageBox(	0,
						_T("One or more portals were clipped because they extend past the hull/bounding box!\n This may lead to snapping issues inside WorldEditor.\n")
						_T("Hint: A custom hull can be used to resolve this issue."),
						_T("Portal clipping warning!"),
						MB_ICONWARNING );

	// Warn the user that one or more portals is not being exported
	if (!bEachOnBoundary)
		::MessageBox(	0,
						_T("One or more portals will not be exported since they are not on a boundary!"),
						_T("Portal error!"),
						MB_ICONERROR );
}